/**
 * 
 */
package edu.fiu.cis.selfhealingdisk;

/**
 * @author vanvorst
 *
 */
public class ModBlockMapper implements BlockMapper {
	//see the example layouts at the end of the file.	
	private final static int PHYSICAL = 0;
	private final static int LOGICAL  = 1;
	
	private final long blocksPerUnit;
	private final long blocksPerParityUnit;
	private final long[] blocksPerGroup=new long[2];
	private final long[] blocksPerPartiton=new long[2];
	
	private final long[] unitsPerGroup=new long[2];
	private final long[] unitsPerPartiton=new long[2];

	private final long groupsPerPartiton;
	
	private final long modIndex;
	private final int parityIndex;
	private final long totalBlocks;

	private final long firstParityBlock;
	private final long lastParityBlock;
	private final long unit_offset_after_parity;
	private final long extra_meta_data_per_group;

	/**
	 * 
	 */
	public ModBlockMapper(Configuration config, long minNumberOfBlocks) {
		this.modIndex=config.getModIndex();	
		this.groupsPerPartiton=this.modIndex;
		this.parityIndex=config.getModParityLocation();
		this.blocksPerUnit=config.getDataUnitSize();
		
		this.unitsPerGroup[LOGICAL]=config.getParityGroupSize()-1;
		this.unitsPerPartiton[LOGICAL]=this.groupsPerPartiton*this.unitsPerGroup[LOGICAL];
		this.blocksPerGroup[LOGICAL]=this.blocksPerUnit*this.unitsPerGroup[LOGICAL];
		this.blocksPerPartiton[LOGICAL]=this.groupsPerPartiton*this.blocksPerGroup[LOGICAL];		
		
		this.unitsPerGroup[PHYSICAL]=config.getParityGroupSize();
		this.unitsPerPartiton[PHYSICAL]=this.groupsPerPartiton*this.unitsPerGroup[PHYSICAL];
		this.firstParityBlock=this.modIndex*this.parityIndex*this.blocksPerUnit;

		//Find out how many extra blocks are needed for each parity group's parity unit
		final long minLogicalUnits  = minNumberOfBlocks/this.blocksPerUnit + ((minNumberOfBlocks%this.blocksPerUnit==0)?0:1);
		final long minLogicalGroups = minLogicalUnits/this.unitsPerGroup[LOGICAL] + ((minLogicalUnits%this.unitsPerGroup[LOGICAL]==0)?0:1);
		final long minLogicalPartitions = minLogicalGroups/this.groupsPerPartiton + ((minLogicalGroups%this.groupsPerPartiton==0)?0:1);

		int extraParityMetaData=0;
		switch(config.getParityFormat()){
		case FRAGMENTED:
			/*Eduardo: this is returning 1 but I don't have any idea of what should be returned. You are supposing a extra block for what?*/
			extraParityMetaData+=FragmentedParityModel.calcExtraBlocksPerGroup(minLogicalGroups, this.unitsPerGroup[PHYSICAL]);
			break;
		case LOG:
			extraParityMetaData+=LogParityModel.calcExtraBlocksPerGroup(minLogicalGroups, this.unitsPerGroup[PHYSICAL]);
			break;
		}
		
		if(config.isIdentityTag()||config.isChecksum()||config.isTimestamp()) {
			switch(config.getMetaDataLocation()) {
			case SAME_UNIT:
				//no op
				break;
			case NEXT_BLOCK:
				/* Eduardo: the returned value is changed in this step? Should be extraParityMetaData++ ?*/
				extraParityMetaData=1;
				break;
			default:
				throw new RuntimeException("Unknown meta data location "+config.getMetaDataLocation());
			}
		}
		this.extra_meta_data_per_group=extraParityMetaData;
		/* Eduardo: Changed this line from this.blocksPerParityUnit=this.blocksPerParityUnit+extraParityMetaData
		 * The parityUnitSize should be the same as unit size + extraMetadata */
		this.blocksPerParityUnit=this.blocksPerUnit+extraParityMetaData;
		this.blocksPerGroup[PHYSICAL]=this.blocksPerUnit*this.unitsPerGroup[PHYSICAL]+extraParityMetaData;
		this.blocksPerPartiton[PHYSICAL]=this.groupsPerPartiton*this.blocksPerGroup[PHYSICAL];		
		
		this.lastParityBlock=this.firstParityBlock+(this.blocksPerUnit+extraParityMetaData)*this.modIndex-1;
		this.unit_offset_after_parity=(this.parityIndex+1)*this.modIndex;
		
		this.totalBlocks=minLogicalPartitions*this.blocksPerPartiton[PHYSICAL];
		
		if(Main.DEBUG) {
		//if(true) {
			System.out.println("	[DEBUG]blocksPerUnit:"+blocksPerUnit);
			System.out.println("	[DEBUG]blocksPerParityUnit:"+blocksPerParityUnit);
			System.out.println("	[DEBUG]blocksPerGroup[PHYSICAL]:"+blocksPerGroup[PHYSICAL]);
			System.out.println("	[DEBUG]blocksPerPartiton[PHYSICAL]:"+blocksPerPartiton[PHYSICAL]);
			System.out.println("	[DEBUG]unitsPerGroup[PHYSICAL]:"+unitsPerGroup[PHYSICAL]);
			System.out.println("	[DEBUG]unitsPerPartiton[PHYSICAL]:"+unitsPerPartiton[PHYSICAL]);
			System.out.println("	[DEBUG]blocksPerGroup[LOGICAL]:"+blocksPerGroup[LOGICAL]);
			System.out.println("	[DEBUG]blocksPerPartiton[LOGICAL]:"+blocksPerPartiton[LOGICAL]);
			System.out.println("	[DEBUG]unitsPerGroup[LOGICAL]:"+unitsPerGroup[LOGICAL]);
			System.out.println("	[DEBUG]unitsPerPartiton[LOGICAL]:"+unitsPerPartiton[LOGICAL]);
			System.out.println("	[DEBUG]groupsPerPartiton:"+groupsPerPartiton);
			System.out.println("	[DEBUG]modIndex:"+modIndex);
			System.out.println("	[DEBUG]parityIndex:"+parityIndex);
			System.out.println("	[DEBUG]totalBlocks:"+totalBlocks);
			System.out.println("	[DEBUG]firstParityBlock:"+firstParityBlock);
			System.out.println("	[DEBUG]lastParityBlock:"+lastParityBlock);
			System.out.println("	[DEBUG]unit_offset_after_parity:"+unit_offset_after_parity);		
			System.out.println("	[DEBUG]extra_meta_data_per_group:"+extra_meta_data_per_group);		
			System.out.println("\t[DEBUG]minNumberOfBlocks:"+minNumberOfBlocks);
			System.out.flush(); 
		}

	}
	

	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#requiredPhysicalBlocks()
	 */
	public long requiredPhysicalBlocks() {
		return this.totalBlocks;
	}
	
	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#getParityIndex()
	 */
	public final int getParityIndex() {
		return parityIndex;
	}
	
	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#getModIndex()
	 */
	public long getModIndex() {
		return this.modIndex;
	}

	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#requiredPartitions()
	 */
	public long requiredPartitions() {
		return this.totalBlocks/this.blocksPerPartiton[PHYSICAL];
	}
	
	
	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#getBlocksPerDataUnit()
	 */
	public long getBlocksPerDataUnit() {
		return blocksPerUnit;
	}

	
	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#getUnitsPerPartition()
	 */
	public long getUnitsPerPartition() {
		return unitsPerPartiton[PHYSICAL];
	}

	
	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#getBlocksPerParityUnit()
	 */
	public long getBlocksPerParityUnit() {
		return blocksPerUnit+extra_meta_data_per_group;
	}

	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#getBlocksPerGroup()
	 */
	public long[] getBlocksPerGroup() {
		return blocksPerGroup;
	}


	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#getGroupsPerPartiton()
	 */
	public long getGroupsPerPartiton() {
		return groupsPerPartiton;
	}


	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#calcUnitFromBlock(long)
	 */
	public long calcUnitFromPhysicalBlock(long physicalBlock) {
		// on the interval [0,this.blocksPerPartiton[PHYSICAL])
		final long partition = physicalBlock/this.blocksPerPartiton[PHYSICAL];
		final long block = physicalBlock%this.blocksPerPartiton[PHYSICAL];
		long unit=0;
		if(this.firstParityBlock>block) {
			//before parity units
			unit=block/this.blocksPerUnit;
		}
		else if(this.firstParityBlock<=block&&this.lastParityBlock>=block) {
			//int parity units
			unit=this.firstParityBlock/this.blocksPerUnit;
			unit+=(block-firstParityBlock)/this.blocksPerParityUnit;
		}
		else if(this.firstParityBlock<block) {
			//after parity units
			unit=this.unit_offset_after_parity+(block-lastParityBlock)/this.blocksPerUnit;
		}
		return partition*this.unitsPerPartiton[PHYSICAL] + unit%this.modIndex;
	}
	
	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#calcUnitFromLogicalBlock(long)
	 */
	public long calcUnitFromLogicalBlock(long logicalBlock) {
		// on the interval [0,this.blocksPerPartiton[LOGICAL])
		final long partition = logicalBlock/this.blocksPerPartiton[LOGICAL];
		final long block = logicalBlock%this.blocksPerPartiton[LOGICAL];
		final long unit=block/this.blocksPerUnit;
		return partition*this.unitsPerPartiton[LOGICAL] + unit%this.modIndex;
	}

	
	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#calcParityGroupFromPhysicalBlock(long)
	 */
	public long calcParityGroupFromPhysicalBlock(long physicalBlock) {
		final long partition = physicalBlock/this.blocksPerPartiton[PHYSICAL];
		final long unit=calcUnitFromPhysicalBlock(physicalBlock)%this.unitsPerPartiton[PHYSICAL];
		return partition*this.groupsPerPartiton+unit%this.modIndex;
	}	
	
	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#calcParityGroupFromLogicalBlock(long)
	 */
	public long calcParityGroupFromLogicalBlock(long logicalBlock) {
		final long partition = logicalBlock/this.blocksPerPartiton[LOGICAL];
		final long unit=calcUnitFromLogicalBlock(logicalBlock)%this.unitsPerPartiton[LOGICAL];
		return partition*this.groupsPerPartiton+unit%this.modIndex;
	}

	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#calcLogicalBlock(long)
	 */
	public long calcLogicalBlock(long physicalBlock) {
		// on the interval [0,this.blocksPerPartiton[PHYSICAL])
		final long partition = physicalBlock/this.blocksPerPartiton[PHYSICAL];
		final long unit=calcParityGroupFromPhysicalBlock(physicalBlock)%this.unitsPerPartiton[PHYSICAL];
		final long group_unit_index = unit % this.unitsPerGroup[LOGICAL];
		long logicalUnit=0;
		if(group_unit_index<this.parityIndex) {
			logicalUnit=unit;
		}
		else if(group_unit_index == this.parityIndex) {
			return -1;
		}
		else {
			logicalUnit=unit-this.groupsPerPartiton;
		}
		return
			partition*this.blocksPerPartiton[LOGICAL]+
		    logicalUnit*this.blocksPerUnit+
		    physicalBlock%this.blocksPerUnit;
	}

	/* (non-Javadoc)
	 * @see edu.fiu.cis.selfhealingdisk.BlockMapper#calcPhysicalBlock(long)
	 */
	public long calcPhysicalBlock(long logicalBlock) {
		final long partition = logicalBlock/this.blocksPerPartiton[LOGICAL];
		final long logicalUnit=calcUnitFromLogicalBlock(logicalBlock)%this.unitsPerPartiton[LOGICAL];
		long physicalUnit=logicalUnit;
		if(logicalUnit % this.unitsPerGroup[LOGICAL] >= this.parityIndex) {
			physicalUnit++;
		}
		return
			partition*this.blocksPerPartiton[PHYSICAL]+
			physicalUnit*this.blocksPerUnit+
			logicalBlock%this.blocksPerUnit;
	}
	
	/**
	 * Eduardo: add this part to get the first and last block for the correspondent parity unit
	 */
	public long[] calcParityBlocks(long physicalBlock) {
		long parityGroup = calcParityGroupFromPhysicalBlock(physicalBlock);
		long fpb = this.firstParityBlock + this.blocksPerParityUnit*parityGroup; 
		return new long[]{fpb,fpb+this.blocksPerParityUnit};
	}
	
	/**
	 * Eduardo: add this method for support stripping parity
	 * If we update the full parity unit I will not need this method
	 */
	public int calcRelPositionInPhysicalUnit(long physicalBlock) {
		final long block = physicalBlock%this.blocksPerPartiton[PHYSICAL];
		int relpos = 0;
		if(this.firstParityBlock>block) {
			//before parity units
			relpos=(int) (block%this.blocksPerUnit);
		}
		else if(this.firstParityBlock<=block&&this.lastParityBlock>=block) {
			//in parity units
			relpos=(int) ((block-firstParityBlock)%this.blocksPerParityUnit);
		}
		else if(this.firstParityBlock<block) {
			//after parity units
			relpos=(int) ((block-lastParityBlock)%this.blocksPerUnit);
		}
		return relpos;
	}
	
	
	
}
//unit size=2, group_size=3, mod_index=3, parity_index=2, extra_meta_blocks=1
//|--------------------------------------------------------------|--------------------------------------------------------------|
//P:|                              0                               |                               1                              |
//|--------------------------------------------------------------|--------------------------------------------------------------|
//U:|    0|    1|    2|    3|    4|    5|       6|       7|       8|    9|   10|   11|   12|   13|   14|      15|      16|      17|
//|-----|-----|-----|-----|-----|-----|--------|--------|--------|-----|-----|-----|-----|-----|-----|--------|--------|--------|
//G:|    0|    1|    2|    0|    1|    2|       0|       1|       2|    3|    4|    5|    3|    4|    5|       3|       4|       5|
//|-----|-----|-----|-----|-----|-----|--------|--------|--------|-----|-----|-----|-----|-----|-----|--------|--------|--------|
//B:| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|37|38|39|40|41|42|
//###############################################################################################################################
//LB:| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|%%%%%%%%%%%%%%%%%%%%%%%%%%|12|13|14|15|16|17|18|19|20|21|22|23|%%%%%%%%%%%%%%%%%%%%%%%%%%|
//|-----|-----|-----|-----|-----|-----|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----|-----|-----|-----|-----|-----|%%%%%%%%%%%%%%%%%%%%%%%%%%|
//LG:|    0|    1|    2|    0|    1|    2|%%%%%%%%%%%%%%%%%%%%%%%%%%|    3|    4|    5|    3|    4|    5|%%%%%%%%%%%%%%%%%%%%%%%%%%|
//|-----|-----|-----|-----|-----|-----|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----|-----|-----|-----|-----|-----|%%%%%%%%%%%%%%%%%%%%%%%%%%|
//LU:|    0|    1|    2|    3|    4|    5|%%%%%%%%%%%%%%%%%%%%%%%%%%|    6|    7|    8|    9|   10|   11|%%%%%%%%%%%%%%%%%%%%%%%%%%|
//|-----------------------------------|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----------------------------------|%%%%%%%%%%%%%%%%%%%%%%%%%%|
//LP:|                 0                 |%%%%%%%%%%%%%%%%%%%%%%%%%%|                  1                |%%%%%%%%%%%%%%%%%%%%%%%%%%|
//|-----------------------------------|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----------------------------------|%%%%%%%%%%%%%%%%%%%%%%%%%%|
//
//P -> Partition #
//U -> Unit #
//G -> Group ID
//B -> Block #
//LB -> Logical Block #
//LG -> Logical Group ID
//LU -> Logical Unit #
//LP -> Logical Partition #


//unit size=2, group_size=3, mod_index=3, parity_index=1, extra_meta_blocks=1
//|--------------------------------------------------------------|--------------------------------------------------------------|
//P:|                              0                               |                               1                              |
//|--------------------------------------------------------------|--------------------------------------------------------------|
//U:|    0|    1|    2|       3|       4|       5|    6|    7|    8|    9|   10|   11|      12|      13|      14|   15|   16|   17|
//|-----|-----|-----|--------|--------|--------|-----|-----|-----|-----|-----|-----|--------|--------|--------|-----|-----|-----|
//G:|    0|    1|    2|       0|       1|       2|    0|    1|    2|    3|    4|    5|       3|       4|       5|    3|    4|    5|
//|-----|-----|-----|--------|--------|--------|-----|-----|-----|-----|-----|-----|--------|--------|--------|-----|-----|-----|
//B:| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|37|38|39|40|41|42|
//###############################################################################################################################
//LB:| 0| 1| 2| 3| 4| 5|%%%%%%%%%%%%%%%%%%%%%%%%%%| 6| 7| 8| 9|10|11|12|13|14|15|16|17|%%%%%%%%%%%%%%%%%%%%%%%%%%|18|19|20|21|22|23|
//|-----|-----|-----|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----|-----|-----|-----|-----|-----|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----|-----|-----|
//LG:|    0|    1|    2|%%%%%%%%%%%%%%%%%%%%%%%%%%|    0|    1|    2|    3|    4|    5|%%%%%%%%%%%%%%%%%%%%%%%%%%|    3|    4|    5|
//|-----|-----|-----|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----|-----|-----|-----|-----|-----|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----|-----|-----|
//LU:|    0|    1|    2|%%%%%%%%%%%%%%%%%%%%%%%%%%|    3|    4|    5|    6|    7|    8|%%%%%%%%%%%%%%%%%%%%%%%%%%|    9|   10|   11|
//|-----------------|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----------------|-----------------|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----------------
//P:|                              0                               |                               1                              |
//|-----------------|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----------------|-----------------|%%%%%%%%%%%%%%%%%%%%%%%%%%|-----------------
//
//P -> Partition #
//U -> Unit #
//G -> Group ID
//B -> Block #
//LB -> Logical Block #
//LG -> Logical Group ID
//LU -> Logical Unit #
//LP -> Logical Partition #
